import java.util.HashMap;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;

public class Scope {
    /** <p>Lista de scopes que sao inferiores a este.</p>
     * 
     * <p>Serve para armazenar scopes como os do while que podem ocorrer
     * dentro de uma funcao.</p>
     * <p>As funcoes nao sao armazenadas nesta variavel.</p>
     */
    private List<Scope> childrenScope = null;

    /** Scope que esta acima deste. */
    private Scope parentScope = null;

    /** Variaveis que existem neste scope. */
    private List<Variable> variablesInScope = null;

    /** Funcoes que existem neste scope.
     * So pode haver funcoes no scope global.
     */
    private List<Function> functionsInScope = null;

    /** Argumentos desta funcao.
     * So pode ser usado se este scope representar o scope "topo" de uma funcao,
     * isto e, nao representa um scope de um "while" ou de um "if".
     */
    private Map<String, String> argumentsOfThisFunction = null;

    /** Nome do scope. */
    private String scopeName = null;

    /**
     * Cria um novo Scope, tendo como pai o Scope passado em <i>parentScope</i> e percorrendo a AST a partir do no passado em <i>node</i>
     * 
     * @param parentScope O Scope pai deste scope
     * @param node O no da arvore a partir do qual vai ser criado este novo Scope
     */
    Scope(Scope parentScope, Node node) {
        this.parentScope = parentScope;

        // Variaveis necessarias para se poder criar os nomes dos scopes como "while1" ou "if5"
        int enconteredWhiles = 0;
        int enconteredIfs = 0;

        for (Node childNode : node.jjtGetChildren()) {
            if (isVariableDeclarationNode(childNode)) {
                // Criar uma lista para guardar as variaveis deste
                // scope caso ainda nao exista
                if (variablesInScope == null)
                    variablesInScope = new LinkedList<Variable>();

                Variable var = new Variable(childNode);
                
                // So adicionar a variavel a variablesInScope esta ainda nao existir nessa list
                if (!variablesInScope.contains(var)) {
                	variablesInScope.add(var);
                }
            }
            else if (hasNeedToCreateNewScope(childNode)) {
            	// Precisa de um novo scope

                if (childNode.toString().equals("Function")) {
                    // Criar uma lista para guardar as funcoes deste
                    // scope caso ainda nao exista
                    if (functionsInScope == null)
                        functionsInScope = new LinkedList<Function>();

                    Function func = new Function(childNode, this);

                    functionsInScope.add(func);
                }
                else { // Token do tipo While ou If
                    // Criar uma lista para guardar as child scopes
                    // deste scope caso ainda nao exista
                    if (childrenScope == null)
                        childrenScope = new LinkedList<Scope>();

                    // Encontrar o token WhileBody, IfBody ou ElseBody
                    for (Node bodyNode : childNode.jjtGetChildren()) {
                        if (bodyNode.toString().equals("WhileBody") ||
                            bodyNode.toString().equals("IfBody") ||
                            bodyNode.toString().equals("ElseBody")) {
                            Scope chdScope = new Scope(this, bodyNode);

                            childrenScope.add(chdScope);

                            // Dar um nome a este scope
                            if (bodyNode.toString().equals("WhileBody")) {
                                chdScope.setScopeName("while" + (enconteredWhiles + 1));
                                enconteredWhiles++;
                            }
                            else if (bodyNode.toString().equals("IfBody")) {
                                chdScope.setScopeName("if" + (enconteredIfs + 1));
                                enconteredIfs++;
                            }
                            else {
                                chdScope.setScopeName("else" + enconteredIfs);
                            }
                        }
                    }
                }
            }
        }
    }

    /** Indica se o node representa uma declaracao de uma variavel.
     * @param node O node a verificar
     * @returns true Se o node representar uma declaracao de variavel, caso contrario devolve false
     */
    private boolean isVariableDeclarationNode(Node node) {
        // Por enquanto so e declaracao de variavel se o valor do
        // token for "Declaration" (provavelmente isto era modificar
        // quando comecar a fazer o parsing das funcoes
        if (node.toString().equals("Declaration") ||
            node.toString().equals("Assign"))
            return true;

        return false;
    }

    /** <p>Indica que o node representa algo que requer a criacao de um novo Scope.</p>
     * 
     * <p>Esse scope e adicionado a <i>childrenScope</i></p>
     * @param node O node a verificar
     * @returns true Se o node representar uma declaracao de variavel, caso contrario devolve false
     */
    private boolean hasNeedToCreateNewScope(Node node) {
        // Indica se e preciso criar um novo scope para o que esta
        // para a frente do no.
        if (node.toString().equals("While") ||
            node.toString().equals("If") ||
            node.toString().equals("ElseBody") ||
            node.toString().equals("Function"))
            return true;
        return false;
    }

	/**
	 * <p>Faz lookup de uma variavel com o nome <i>name</i>.</p>
	 * 
	 * <p>O lookup e feito primeiro sobre as variaveis que estao definidas neste
	 * Scope, seguido de uma pesquisa (no caso de este Scope ser um scope de
	 * topo de uma funcao) nas variaveis dos argumentos da funcao.</p>
	 * 
	 * <p>Caso nao encontre essa variavel, tenta-se encontrar-la nos Scopes que se
	 * encontram a um nivel mais elevado que este.</p>
	 * 
	 * @param name
	 *            Nome da variavel a procurar
	 * @return O objecto Variable que representa a variavel que foi pedida, ou
	 *         null caso nao consiga encontrar-la
	 */
    public Variable lookup(String name) {
        if (variablesInScope != null) {
            for (Variable var : variablesInScope) {
                if (var.getVariableName().equals(name)) {
                    return var;
                }
            }
        }

        // Procurar nos argumentos da funcao, se estes existirem
        if (argumentsOfThisFunction != null && argumentsOfThisFunction.containsKey(name)) {
            return new Variable(name, argumentsOfThisFunction.get(name));
        }

        // Variavel nao se encontra neste scope, tentar encontrar no scope acima deste
        if (parentScope != null) {
            return parentScope.lookup(name);
        }
        else
            return null;
    }

	/**
	 * <p>Obtem uma lista das funcoes que estao definidas neste scope.</p>
	 * 
	 * <p>Como nao e possivel, em Yal, definir funcoes dentro de funcoes, esta
	 * funcao so e util quando este scope representa um scope de topo de um
	 * ficheiro.</p>
	 * 
	 * @return Uma Lista contendo as funcoes definidas neste scope, ou null se
	 *         nao houver nenhuma funcao definida neste scope
	 */
    public List<Function> getFunctionsInScope() {
        return functionsInScope;
    }

    /** 
     * <p>Adiciona argumentos de uma funcao a este scope.</p>
     * 
     * @param variableName Nome da variavel a adicionar
     * @param variableType tipo da variavel a adicionar (pode ser "int" ou "int[]")
     */
    public void addArgumentToScope(String variableName, String variableType) {
        if (argumentsOfThisFunction == null) {
            argumentsOfThisFunction = new HashMap<String, String>();
        }

        argumentsOfThisFunction.put(variableName, variableType);
    }

    /**
     * Devolve o nome deste scope.
     * 
     * @return O nome deste scope.
     */
    public String getScopeName() {
        return scopeName;
    }

    /**
     * Modifica o nome deste scope.
     * 
     * @param scopeName O nome a dar a este scope
     */
    public void setScopeName(String scopeName) {
        this.scopeName = scopeName;
    }

	/**
	 * Devolve uma lista contendo os scopes filhos deste scope, ou seja, devolve
	 * os scopes que estao imediatamente um nivel inferior a este.
	 * 
	 * @return Uma lista de scopes, ou null caso nao existam
	 */
    public List<Scope> getChildrenScope() {
    	return childrenScope;
    }
    
    /**
     * Obtem um scope filho atraves do nome.
     * 
     * @param scopeName O nome do scope a procurar
     * @return Um Scope cujo nome e igual ao passado pelo argumento <i>scopeName</i>
     */
    public Scope getChildrenScopeByName(String scopeName) {
        if (childrenScope != null) {
            for (Scope childScope : childrenScope) {
                if (childScope.getScopeName().equals(scopeName))
                    return childScope;
            }
        }
        return null;
    }

    /**
     * Devolve uma lista contendo as variaveis que estao definidas neste scope.
     * 
     * @return Uma lista de Variable, ou null caso nao existam
     */
    public List<Variable> getVariablesInScope() {
        return variablesInScope;
    }
}

class Function {
	
	/** O nome desta funcao. */
	private String functionName = null;

	/**
	 * <p>
	 * O nome da variavel Yal returnada por esta funcao.
	 * </p>
	 * 
	 * <p>
	 * Se for null, entao esta funcao nao devolve nada.
	 * </p>
	 */
	private String returnName = null;

	/**
	 * <p>
	 * Uma string representando o tipo de variavel que e retornada por esta
	 * funcao.
	 * </p>
	 * 
	 * <p>
	 * So pode ter 3 valores: "int", "int[]" ou null.
	 * </p>
	 * 
	 * <p>
	 * Se for null, entao esta funcao nao devolve nada.
	 * </p>
	 */
	private String returnType = null;

	/**
	 * <p>
	 * Um Map que mapeia o nome de um argumento desta funcao para o seu tipo.
	 * </p>
	 * 
	 * <p>
	 * Se esta variavel for null, entao esta funcao nao recebe argumentos.
	 * </p>
	 * 
	 * <p>
	 * Os unicos valores que as chaves podem mapear sao "int" e "int[]".
	 * </p>
	 */
	private Map<String, String> arguments = null;

	/**
	 * <p>
	 * O tipo dos argumentos desta funcao pela ordem que sao declarados no
	 * ficheiro Yal.
	 * </p>
	 * 
	 * <p>
	 * Esta variavel <b>nao</b> armazena o nome das variaveis, so os seus tipos.
	 * </p>
	 * 
	 * <p>
	 * Se esta variavel for null, entao esta funcao nao recebe argumentos.
	 * </p>
	 * 
	 * <p>
	 * Caso contrario, esta variavel podera ser composta pelos valores "int" e
	 * "int[]".
	 * </p>
	 */
	// Usada para se poder fazer parte da analise semantica
    private List<String> argumentsInOrder = null;
    
    /** O scope topo desta funcao. */
    private Scope functionScope = null;

    Function (Node node, Scope currentScope) {
        for (Node childNode : node.jjtGetChildren()) {
            if (childNode.toString().equals("IDFunction_sem_return")) {
                SimpleNode SNchildNode = (SimpleNode)childNode;
                functionName = SNchildNode.val;
            }
            else if (childNode.toString().equals("IDFunction_com_return")) {
                SimpleNode SNchildNode = (SimpleNode)childNode;
                functionName = SNchildNode.val;
            }
            if (childNode.toString().equals("IDScalarElement")) {
                SimpleNode SNchildNode = (SimpleNode)childNode;
                returnName = SNchildNode.val;
                returnType = "int";
            }
            if (childNode.toString().equals("IDArrayElement")) {
                SimpleNode SNchildNode = (SimpleNode)childNode;
                returnName = SNchildNode.val;
                returnType = "int[]";
            }

            if (childNode.toString().equals("Varlist")) {
                arguments = new HashMap<String, String>();
                argumentsInOrder = new LinkedList<String>();
                
                processVarlist(childNode);
                


                // Se ja exister um functionScope nesta funcao, adicionar os argumentos a esse scope
                if (functionScope != null) {
                    for (Map.Entry<String, String> argument : arguments.entrySet()) {
                        String argumentName = argument.getKey();
                        String argumentType = argument.getValue();

                        functionScope.addArgumentToScope(argumentName, argumentType);
                    }
                }
            }

            if (childNode.toString().equals("FunctionBody")) {
                // Confirmar que a funcao tem corpo
                if (childNode.jjtGetNumChildren() != 0) {
                    functionScope = new Scope(currentScope, childNode);

                    // Adicionar os argumentos (se existirem) desta funcao ao scope agora criado
                    if (arguments != null) {
                        for (Map.Entry<String, String> argument : arguments.entrySet()) {
                            String argumentName = argument.getKey();
                            String argumentType = argument.getValue();

                            functionScope.addArgumentToScope(argumentName, argumentType);
                        }
                    }
                }
            }
        }
    }

	/**
	 * Guardar os argumentos desta funcao.
	 * 
	 * @param node
	 *            Node que aponta para o token Varlist de uma funcao
	 */
	private void processVarlist(Node node) {
		for (Node childNode : node.jjtGetChildren()) {
			if (childNode.toString().equals("IDScalarElement")) {
				SimpleNode SNchildNode = (SimpleNode) childNode;
				arguments.put(SNchildNode.val, "int");
				argumentsInOrder.add(SNchildNode.val);
			} else if (childNode.toString().equals("IDArrayElement")) {
				SimpleNode SNchildNode = (SimpleNode) childNode;
				arguments.put(SNchildNode.val, "int[]");
				argumentsInOrder.add(SNchildNode.val);
			}
		}
	}

	/**
	 * Devolve o nome desta funcao Yal.
	 * 
	 * @return Uma string contendo o nome desta funcao Yal
	 */
	public String getFunctionName() {
		return functionName;
	}

	/**
	 * <p>
	 * Devolve o nome da variavel que e devolvida por esta funcao Yal.
	 * </p>
	 * 
	 * <p>
	 * Se esta funcao Yal nao devolver nenhuma variavel, entao esta funcao
	 * (<i>getReturnName</i>) devolve null.
	 * </p>
	 * 
	 * @return Uma string contendo o nome da variavel de return da funcao Yal
	 */
	public String getReturnName() {
		return returnName;
	}

	/**
	 * <p>
	 * Devolve o tipo de retorno da variavel que e devolvida por esta funcao
	 * Yal.
	 * </p>
	 * 
	 * <p>
	 * Se esta funcao Yal nao devolver nenhuma variavel, entao esta funcao
	 * (<i>getReturnType</i>) devolve null.
	 * </p>
	 * 
	 * @return Uma string, que pode ter o valor "int" ou "int[]" e que indica o
	 *         tipo de retorno da funcao Yal
	 */
	public String getReturnType() {
		return returnType;
	}

	/**
	 * <p>
	 * Devolve um Map que mapeia o nome dos argumentos desta funcao para os seus
	 * respectivos tipos.
	 * </p>
	 * 
	 * <p>
	 * Se for null, entao esta funcao Yal nao recebe nenhuns argumentos.
	 * </p>
	 * 
	 * @return Um Map que contem nas chaves os nomes dos argumentos desta funcao
	 *         Yal e mapeia-os para os seus respectivos tipos (que so pode ser
	 *         "int" ou "int[]")
	 */
	public Map<String, String> getArguments() {
		return arguments;
	}

	/**
	 * <p>
	 * Devolve uma lista de strings que contem os tipos dos argumentos desta
	 * funcao Yal, tal como foram definidos no ficheiro Yal.
	 * </p>
	 * 
	 * <p>
	 * Se for null, entao esta funcao Yal nao recebe nenhuns argumentos.
	 * </p>
	 * 
	 * @return Uma lista com o tipo de argumentos desta funcao Yal. So pode
	 *         conter os valores "int" ou "int[]"
	 */
	public List<String> getArgumentsInOrder() {
		return argumentsInOrder;
	}

	/**
	 * Devolve o Scope de topo desta funcao.
	 * 
	 * @return um Scope correspondente ao scope topo desta funcao
	 */
	public Scope getFunctionScope() {
		return functionScope;
	}

	/**
	 * <p>
	 * Obter um scope interno da funcao.
	 * </p>
	 * 
	 * <p>
	 * Cada scope interno tem um nome, que corresponde a palavra chave que o
	 * criou, juntamente com o numero do scope.
	 * </p>
	 * 
	 * <p>
	 * Por exemplo, considerando esta funcao:
	 * </p>
	 * 
	 * <pre>
	 *     function foo() {
	 *         while (bar) {
	 *             ...
	 *             if (xpto) {
	 *                 ...
	 *             }
	 *         }
	 *         while (quax) {
	 *             ...
	 *             if (lol) {
	 *                 ...
	 *             }
	 *         }
	 *     }
	 * </pre>
	 * 
	 * <p>
	 * Para se aceder ao scope do "while (bar)", o argumento scopeList teria de
	 * ser igual a ["while1"].
	 * </p>
	 * 
	 * <p>
	 * Para se aceder ao scope do "if (xpto)", o argumento scopeList teria de
	 * ser igual a ["while1", "if1"].
	 * </p>
	 * 
	 * <p>
	 * Para se aceder ao scope do "while (quax)", o argumento scopeList teria de
	 * ser igual a ["while2"].
	 * </p>
	 * 
	 * <p>
	 * E por fim, para se aceder ao scope do "if (lol)", o argumento scopeList
	 * teria de ser igual a ["while2", "if1"].
	 * </p>
	 * 
	 * @param scopeList
	 *            Uma lista de Strings que especifica o scope a obter usando a
	 *            descricao efectuada acima
	 * @return O Scope pedido caso este exista, caso contrario devolve null
	 */
	public Scope getInsideScope(List<String> scopeList) {
		Scope scopeActual = null;

		for (String scopeName : scopeList) {
			if (scopeActual == null) {
				scopeActual = functionScope.getChildrenScopeByName(scopeName);
			} else {
				scopeActual = scopeActual.getChildrenScopeByName(scopeName);
			}
		}
		return scopeActual;
	}

	/**
	 * Devolve o Scope de topo desta funcao.
	 * 
	 * @return um Scope correspondente ao scope topo desta funcao
	 */
	public Scope getInsideScope() {
		return getFunctionScope();
	}

	// Primariamente usado para debug
	@Override
	public String toString() {
		String stringAImprimir = null;

		stringAImprimir = "Nome desta funcao: " + functionName + "\n";
		stringAImprimir += "Nome do return desta funcao: " + returnName + "\n";
		stringAImprimir += "Tipo do return desta funcao: " + returnType + "\n";

        if (arguments != null) {
            int k = 0;

            for (Map.Entry<String, String> argument : arguments.entrySet()) {
                stringAImprimir += "Argument[" + k + "]: " + argument.getKey() + " -> " + argument.getValue() + "\n";
                k++;
            }
        }

        else {
            stringAImprimir += "Sem argumentos";
        }

        return stringAImprimir;
    }
}

class Variable {
	/** O nome desta variavel de Yal */
    private String variableName = null;
    
    /** O tipo desta variavel de Yal.
     * 
     * So pode ser "int" ou "int[]".
     */
    private String variableType = null;

    // So usado quando a variavel e um array
    /** O indice desta variavel de YAL, caso esta seja um array. */
    private String variableIndex = null;
    
	/**
	 * <p>O tamanho desta variavel de YAL, caso esta seja um array.</p>
	 * 
	 * <p>Esta variavel so e usado nas declaracoes de variaveis Yal.</p>
	 * 
	 * <p>Nas situacoes em que a variavel de YAL e um array que ja esta declarado,
	 * e o numero representa um acesso a um indice, e usada a variavel
	 * <i>variableIndex</i> em vez desta.</p>
	 */
	private String variableSize = null;

	/**
	 * Cria uma nova variavel a partir de um no da arvore.
	 * 
	 * @param node
	 *            O no a usar para criar esta variavel
	 */
	public Variable(Node node) {
		if (isGlobalVariable(node))
			processGlobalVariable(node);
		else
			processLocalVariable(node);
	}

	/**
	 * <p>
	 * Cria uma variavel indicando o seu nome e o seu tipo.
	 * </p>
	 * 
	 * <p>
	 * O seu tipo so pode ser "int" ou "int[]".
	 * </p>
	 * 
	 * @param name
	 *            O nome que esta variavel ira ter
	 * @param type
	 *            O tipo que esta variavel ira ter
	 */
	public Variable(String name, String type) {
		variableName = name;
		variableType = type;
	}

	/**
	 * Indica se um Node corresponde a uma declaracao de uma variavel global ao
	 * modulo (ie. um field).
	 * 
	 * @param node
	 *            O node a analisar
	 * @return true caso <i>node</i> corresponda a uma declaracao de uma
	 *         variavel global
	 */
	private boolean isGlobalVariable(Node node) {
		// Uma variavel global e uma que e definida definida num node
		// do tipo Declaration
		return node.toString().equals("Declaration");
	}

	/**
	 * Preencher os campos deste objecto como se este representasse uma variavel
	 * de Yal global.
	 * 
	 * @param node
	 *            O node que representa a variavel global
	 */
	private void processGlobalVariable(Node node) {
		boolean isArray = false;

		for (Node childNode : node.jjtGetChildren()) {

			if (childNode.toString().equals("IDScalarElement")) {
				SimpleNode SNchildNode = (SimpleNode) childNode;
				variableName = SNchildNode.val;
			}
			else if (childNode.toString().equals("ArraySizeInteger"))
				isArray = true;
		}

		if (isArray)
			variableType = "int[]";
		else
			variableType = "int";
	}

	/**
	 * Preencher os campos deste objecto como se este representasse uma variavel
	 * de Yal local a uma funcao.
	 * 
	 * @param node
	 *            O node que representa a variavel local
	 */
    private void processLocalVariable(Node node) {
		// Variaveis locais sao indicadas pelo node do tipo Assign(?)
		boolean isArray = false;

		// TODO: Arranjar uma maneira melhor de fazer isto
		for (Node childNode : node.jjtGetChildren()) {

            // Pode ter so os tokens #LeftAssignSide e #RightAssignSide
            if (childNode.toString().equals("LeftAssignSide")) {
                for (Node leftAssignBody : childNode.jjtGetChildren()) {

                    // Pode ter so os tokens #ArrayAccess e #ScalarAccess
                    if (leftAssignBody.toString().equals("ScalarAccess")) {
                        for (Node scalarAccessBody : leftAssignBody.jjtGetChildren()) {

                            // Pode ter so os tokens #IDScalarAccess e #MethodScalarAccess
                            if (scalarAccessBody.toString().equals("IDScalarAccess")) {
                                SimpleNode SNode = (SimpleNode)scalarAccessBody;
                                variableName = SNode.val;
                            }

                            // E uma chamada a um metodo
                            else {}
                        }
                    }
                    else {
                        // Token #ArrayAccess
                        isArray = true;

                        for (Node arrayAccessBody : leftAssignBody.jjtGetChildren()) {
                            if (arrayAccessBody.toString().equals("IDArrayAccess")) {
                                SimpleNode SNode = (SimpleNode)arrayAccessBody;
                                variableName = SNode.val;
                            }
                            if (arrayAccessBody.toString().equals("IDIndex")) {
                                SimpleNode SNode = (SimpleNode)arrayAccessBody;
                                variableIndex = SNode.val;
                            }
                        }
                    }
                }
            }
            // Examinar o #RightAssignSide
            else {
                for (Node rightAssignSide : childNode.jjtGetChildren()) {
                    if (rightAssignSide.toString().equals("ArraySizeScalarVariable")) {
                        isArray = true;

                        for (Node arrayAccessBody : rightAssignSide.jjtGetChildren()) {
                            if (arrayAccessBody.toString().equals("ScalarAccess")) {

                                for (Node scalarAccessBody : arrayAccessBody.jjtGetChildren()) {
                                    if (scalarAccessBody.toString().equals("IDScalarAccess")) {
                                        SimpleNode SNode = (SimpleNode)scalarAccessBody;
                                        variableSize = SNode.val;
                                    }
                                }
                            }
                        }
                        // TODO: Obter o tamanho do array
                    }
                }
            }
        }

        if (isArray)
            variableType = "int[]";
        else
            variableType = "int";
    }


	/**
	 * Devolve o nome desta variavel de Yal.
	 * 
	 * @return Uma string correspondendo ao nome desta variavel de Yal
	 */
	public String getVariableName() {
		return variableName;
	}

	/**
	 * Devolve o tipo desta variavel de Yal.
	 * 
	 * @return Uma string, que so pode tomar os valores "int" ou "int[]", que indique o tipo desta variavel de Yal
	 */
    public String getVariableType() {
        return variableType;
    }

	/**
	 * <p>
	 * Devolve o indice desta variavel de Yal.
	 * </p>
	 * 
	 * <p>
	 * Se este objecto nao representar um uso de uma variavel do tipo array, mas
	 * sim uma declaracao, entao esta funcao devolve null.
	 * </p>
	 * 
	 * <p>
	 * Esta funcao tambem devolve null se o tipo de variavel desta funcao for
	 * "int".
	 * </p>
	 * 
	 * @return Uma string contendo o indice de acesso ao array de Yal.
	 */
	public String getVariableIndex() {
		return variableIndex;
	}

    // Primariamente usado para debug
    @Override
	public String toString() {
        String stringAImprimir = null;

        stringAImprimir = "Nome desta var: " + variableName + "\n";
        stringAImprimir += "Tipo desta var: " + variableType;

        if (variableIndex != null) {
            stringAImprimir += "\nIndice do array: " + variableIndex;
        }
        if (variableSize != null) {
            stringAImprimir += "\nTamanho do array: " + variableSize;
        }

        return stringAImprimir;
    }
}
